"""UMB message sender."""

import argparse
import os
import re

from cki_lib import logger
from cki_lib import messagequeue
from cki_lib import metrics
from cki_lib import misc
from cki_lib import session
from cki_lib import stomp
from cki_lib.gitlab import parse_gitlab_url
from cki_lib.kcidb import checks
import datawarehouse

from cki.kcidb.utils import unknown_issues

from . import jira

LOGGER = logger.get_logger(__name__)
SESSION = session.get_session(__name__)


def dw_checkout(checkout_id):
    """Return a DW checkout."""
    return datawarehouse.Datawarehouse(
        os.environ['DATAWAREHOUSE_URL'],
        token=os.environ['DATAWAREHOUSE_TOKEN_UMB_MESSENGER'],
        session=SESSION).kcidb.checkouts.get(id=checkout_id).all.get()


def report(message_type, checkout_id):
    # pylint: disable=too-many-branches
    """Send a UMB message about a CKI build."""
    LOGGER.info('Gathering data for %s: %s', message_type, checkout_id)

    dw_all = dw_checkout(checkout_id)
    checkout = dw_all.checkouts[0]
    if not checkout.valid:
        LOGGER.debug('  ignoring, checkout is not valid')
        return
    if not dw_all.builds:
        LOGGER.debug('  ignoring, no builds available')
        return

    message = {
        'ci': {
            'name': 'CKI (Continuous Kernel Integration)',
            'team': 'CKI',
            'docs': 'https://cki-project.org',
            'url': 'https://gitlab.com/cki-project',
            'irc': 'Slack #team-kernel-cki',
            'email': 'cki-project@redhat.com',
        },
        'run': {
            'url': f'{os.environ["DATAWAREHOUSE_URL"]}/kcidb/checkouts/{checkout.misc["iid"]}',
        },
        'artifact': {
            'type': 'cki-build',
            'issuer': checkout.attributes.get('contacts', ['CKI'])[0],
            'component': checkout.misc.get('source_package_name', 'kernel'),  # deprecated
            'source_package_name': checkout.misc.get('source_package_name', 'kernel'),
            'variant': sorted({b.misc.get('package_name', 'kernel') for b in dw_all.builds})[0],
        },
        'system': [{  # deprecated
            'os': dw_all.builds[0].misc.get('kpet_tree_name', ''),  # deprecated
            'stream': dw_all.builds[0].misc.get('kpet_tree_name', ''),  # deprecated
        }],
        'checkout_id': checkout.id,
        'tree_name': checkout.tree_name,
        'build_info': [],
        'patch_urls': None,
        'merge_request': {
            'merge_request_url': '',
            'is_draft': False,
            'subsystems': [],
            'jira': [],
            'bugzilla': [],
        },
        'branch': checkout.git_repository_branch or '',
        'modified_files': [f['path'] for f in checkout.misc.get('patchset_modified_files', [])],
        'cki_finished': message_type == 'post_test',
        'type': 'build',
        'category': 'kernel-build',
        'namespace': 'cki',
        'generated_at': misc.now_tz_utc().isoformat(),
        'version': '0.1.0'
    }

    for optional_field in ('source_package_version', 'source_package_release'):
        if value := checkout.misc.get(optional_field):
            message['artifact'][optional_field] = value

    # Data specific to merge requests.
    if mr_data := checkout.misc.get('related_merge_request'):
        message['merge_request']['merge_request_url'] = mr_data['url']
        _, mr_object = parse_gitlab_url(mr_data['url'])
        message['merge_request']['is_draft'] = mr_object.work_in_progress
        message['patch_urls'] = [mr_data['diff_url']]

        # Get BZ info, if provided
        message['merge_request']['bugzilla'] = re.findall(
            r'^Bugzilla:\s*(https?://[^\s]*)[\s]*$',
            mr_object.description,
            re.MULTILINE
        )

        # Get Jira info, if provided
        message['merge_request']['jira'] = re.findall(
            r'^JIRA:\s*(https?://[^\s]*)[\s]*$',
            mr_object.description,
            re.MULTILINE
        )
        message['merge_request']['qa_contacts'] = jira.qa_contacts(message['merge_request']['jira'])

        # Get subsystem labels, if any
        message['merge_request']['subsystems'] = [
            label.split(':')[1] for label in mr_object.labels
            if label.startswith('Subsystem:')
        ]

    for build in dw_all.builds:
        # skip if kpet decided that an arch should not be tested
        if build.misc.get('testing_skipped_reason') == 'unsupported':
            continue

        # Just skip noarch
        if build.architecture == "noarch":
            continue

        # build.output_files may or may not be available depending on whether
        # we have any other uploaded files; according to KCIDB schema empty
        # attributes get removed.
        if not (kernel_file_data := next((f for f in (build.output_files or [])
                                          if f['name'] == 'kernel_package_url'), None)):
            # The build failed, or something in the pipeline or data upload
            # went wrong. Either way, there is no build to test.
            LOGGER.warning('No data available for %s!', checkout.id)
            LOGGER.info('Not sending messages for incomplete checkouts')
            return

        build_info = {
            'architecture': build.architecture,
            'build_id': build.id,
            'debug_kernel': build.misc.get('debug', False),
            'kernel_package_url': kernel_file_data['url'],
            'variant': build.misc.get('package_name', 'kernel'),  # deprecated
            'package_name': build.misc.get('package_name', 'kernel'),
        }

        for optional_field in ('package_version', 'package_release'):
            if value := build.misc.get(optional_field):
                build_info[optional_field] = value

        message['build_info'].append(build_info)

    if message_type == 'post_test':
        tests_missing_triage = unknown_issues(dw_all)
        if (
            any(test.status == "FAIL" for test in tests_missing_triage)
            # If all boots from at least one build didn't complete, consider it a fail
            or checks.broken_boot_tests(dw_all=dw_all)
        ):
            message['status'] = 'fail'
        elif any(test.status == "ERROR" for test in tests_missing_triage):
            message['status'] = 'error'
        else:
            message['status'] = 'success'

    topic = '/topic/VirtualTopic.eng.cki.ready_for_test'

    if misc.is_production():
        LOGGER.info('Sending message to %s', topic)
        stomp.StompClient().send_message(message, topic)
    else:
        LOGGER.info('Production mode would send %s to %s', message, topic)


def process_message(body=None, **_):
    """Filter and process messages if requested."""
    object_type = body['object_type']
    object_id = misc.get_nested_key(body, 'object/id')

    LOGGER.info('Processing message for %s %s', object_type, object_id)

    if object_type != 'checkout':
        LOGGER.debug('  ignoring, unsupported object type: %s', object_type)
        return

    if misc.get_nested_key(body, 'object/misc/retrigger', False):
        LOGGER.debug('  ignoring, retriggered checkout')
        return

    match body['status']:
        case 'build_setups_finished':
            report('pre_test', object_id)
        case  'ready_to_report':
            report('post_test', object_id)
        case status:
            LOGGER.debug('  ignoring, unsupported message status: %s', status)


def main(argv: list[str] | None = None) -> None:
    """Run main loop."""
    parser = argparse.ArgumentParser(description='Send UMB messages for CKI results')
    parser.add_argument('--message-type', choices=['pre_test', 'post_test'],
                        help='Message type to send')
    parser.add_argument('--checkout-id', help='Checkout ID to report')
    args = parser.parse_args(argv)

    if args.message_type and args.checkout_id:
        report(args.message_type, args.checkout_id)
        return

    metrics.prometheus_init()

    messagequeue.MessageQueue().consume_messages(
        os.environ.get('WEBHOOK_RECEIVER_EXCHANGE', 'cki.exchange.webhooks'),
        os.environ['UMB_MESSENGER_ROUTING_KEYS'].split(),
        process_message,
        queue_name=os.environ['UMB_MESSENGER_QUEUE'])
